package me.ycy.android.shuffle.synchronizer
import java.util.concurrent.LinkedBlockingQueue
import java.util.Date
import ektorp._
import org.ektorp.support._
import android.util.Log
import org.ektorp._

sealed abstract class Change
case class LocalChange(item: ShuffleItem, timestamp: Date) extends Change
case class RemoteChange(item: ShuffleItem, seq: Long) extends Change
case class RemoteDelete(id: Long, seq: Long) extends Change


class SyncWorker(s: SyncImpl) extends Thread {
  val Tag = "ShuffleSync"

  private val queue: LinkedBlockingQueue[Change] = new LinkedBlockingQueue()

  // NOTE: function onLocalChange have bad performance when local has lot
  // update. callback many time but work queue process not fast enough and
  // duplicated task may append into queue. so make queue unique.
  def put(task: Change) = queue.synchronized {
    if (!queue.contains(task)) queue.put(task)
  }

  private def applyChange(change: Change) = change match {
    case LocalChange(item, timestamp) => applyLocalChange(item, timestamp)
    case RemoteChange(item, seq) => applyRemoteChange(item, seq)
    case RemoteDelete(id, seq) => applyRemoteDelete(id, seq)
  }

  def waitDb() {
    while (s.db == null) {
      Thread.sleep(500)
    }
  }

  private def applyLocalChange(item: ShuffleItem, timestamp: Date) = {
    var flag = true
    while (flag) {
      try {
        var skip = false
        waitDb()
        try {
          val i = s.db.get(classOf[ShuffleItem], item.getId)
          item.revision = i.revision
          if (item.modifiedDate.getTime <= i.modifiedDate.getTime) {
            skip = true
            Log.i(Tag, "local change( " + item.modifiedDate +
                  ") not new than remote(" +
                  i.modifiedDate + ")")
          }
        }
        catch {
          case e: DocumentNotFoundException =>
            // NOTE: Maybe item is permanent delete from shuffle,
            // this is not support now. in order to do this, delete the document
            // from couchdb side.
        }
        if (!skip) {
          s.db.update(item)
          Log.i(Tag, "local change applied: " + item)
        }
        flag = false
      }
      catch {
        case e: UpdateConflictException => {
          // this might should happen
          Log.i(Tag, "Conflict, should rare happen")
        }
        case e => {
          Log.e(Tag, "apply local change exception: " + e.getMessage)
          e.printStackTrace
          flag = false
        }
      }
    }
    // success
    s.cf.timestamp = timestamp
  }

  private def applyRemoteChange(item: ShuffleItem,
                                seq: Long) = s.contentLock.synchronized {
    Log.d(Tag, "begin apply remote change(" + seq + "): " + item)

    Shuffle.storeItem(item, s.cr)
    // success
    s.cf.seq = seq
  }

  private def applyRemoteDelete(id: Long,
                                seq: Long) = s.contentLock.synchronized {
    Log.d(Tag, "begin apply remote delete(" + seq + "): " + id)

    try {
      var cnt = 0
      val sel = Shuffle.CommonColumns.TracksId + " = ?"
      val args = Array(id.toString)
      // try delete from each uri
      cnt += s.cr.delete(Shuffle.Tasks.uri, sel, args)
      cnt += s.cr.delete(Shuffle.Contexts.uri, sel, args)
      cnt += s.cr.delete(Shuffle.Projects.uri, sel, args)
      Log.i(Tag, "Delete total " + cnt + " item, with id " + id)
    }
    catch {
      case e => {
        Log.e(Tag, "delete faild(will ignore): " + e.getMessage)
        e.printStackTrace
      }
    }
    // success
    s.cf.seq = seq
  }

  override def run = {
    var flag = true
    Log.i(Tag, "sync worker started.")
    while (flag) {
      try {
        val c = queue.take
        applyChange(c)
      }
      catch {
        case e: InterruptedException =>
          flag = false
        case e =>
          e.printStackTrace
      }
    }
    Log.i(Tag, "sync worker exit.")
  }
}

